use opentelemetry::{KeyValue, global, trace::TracerProvider as _};
use opentelemetry_sdk::{
    Resource,
    metrics::{MeterProviderBuilder, PeriodicReader, SdkMeterProvider},
    trace::{RandomIdGenerator, Sampler, SdkTracerProvider},
};
use opentelemetry_semantic_conventions::{SCHEMA_URL, attribute::SERVICE_VERSION};
use tracing_core::{Level, LevelFilter};
use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer};
use tracing_subscriber::{EnvFilter, Layer};
use tracing_subscriber::{filter::Targets, layer::SubscriberExt, util::SubscriberInitExt};

// Create a Resource that captures information about the entity for which telemetry is recorded.
fn resource() -> Resource {
    Resource::builder()
        .with_service_name(std::env::var("OTEL_SERVICE_NAME").unwrap_or_default())
        .with_schema_url(
            [KeyValue::new(
                SERVICE_VERSION,
                std::env::var("OTEL_SERVICE_VERSION").unwrap_or_default(),
            )],
            SCHEMA_URL,
        )
        .build()
}

// Construct MeterProvider for MetricsLayer
fn init_meter_provider() -> SdkMeterProvider {
    let exporter = opentelemetry_otlp::MetricExporter::builder()
        .with_http()
        .with_temporality(opentelemetry_sdk::metrics::Temporality::default())
        .build()
        .unwrap();

    let reader = PeriodicReader::builder(exporter)
        .with_interval(std::time::Duration::from_secs(30))
        .build();

    // For debugging in development
    let stdout_reader =
        PeriodicReader::builder(opentelemetry_stdout::MetricExporter::default()).build();

    let meter_provider = MeterProviderBuilder::default()
        .with_resource(resource())
        .with_reader(reader)
        .with_reader(stdout_reader)
        .build();

    global::set_meter_provider(meter_provider.clone());

    meter_provider
}

// Construct TracerProvider for OpenTelemetryLayer
fn init_tracer_provider() -> SdkTracerProvider {
    let exporter = opentelemetry_otlp::SpanExporter::builder()
        .with_http()
        .build()
        .unwrap();

    SdkTracerProvider::builder()
        // Customize sampling strategy
        .with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased(
            1.0,
        ))))
        // If export trace to AWS X-Ray, you can use XrayIdGenerator
        .with_id_generator(RandomIdGenerator::default())
        .with_resource(resource())
        .with_batch_exporter(exporter)
        .build()
}

// Initialize tracing-subscriber and return OtelGuard for opentelemetry-related termination processing
fn init_tracing_subscriber() -> OtelGuard {
    let tracer_provider = init_tracer_provider();
    let meter_provider = init_meter_provider();

    let tracer = tracer_provider.tracer("tracing-otel-subscriber");
    // 创建分层过滤器：允许应用代码使用更详细的日志级别，但限制 OpenTelemetry 相关库的日志级别
    let stdout_filter = EnvFilter::builder()
        .with_default_directive(LevelFilter::INFO.into()) // 应用默认 INFO 级别
        .from_env_lossy();

    // 为 OpenTelemetry layer 创建专门的过滤器
    let otel_layer_filter = Targets::new()
        .with_target("opentelemetry_sdk", Level::INFO)
        .with_default(Level::TRACE);       // 其他所有库默认 TRACE

    tracing_subscriber::registry()
        // 全局基础过滤器 - 防止网络栈重入
        .with(LevelFilter::DEBUG)
        // 控制台输出层 - 仅INFO
        .with(
            tracing_subscriber::fmt::layer()
                .with_filter(stdout_filter)
        )
        // OpenTelemetry 层 - 使用目标过滤器
        .with(
            OpenTelemetryLayer::new(tracer)
                .with_filter(otel_layer_filter.clone())
        )
        // Metrics 层 - 使用目标过滤器
        .with(
            MetricsLayer::new(meter_provider.clone())
                .with_filter(otel_layer_filter)
        )
        .init();

    OtelGuard {
        tracer_provider,
        meter_provider,
    }
}

struct OtelGuard {
    tracer_provider: SdkTracerProvider,
    meter_provider: SdkMeterProvider,
}

impl Drop for OtelGuard {
    fn drop(&mut self) {
        if let Err(err) = self.tracer_provider.shutdown() {
            eprintln!("{err:?}");
        }
        if let Err(err) = self.meter_provider.shutdown() {
            eprintln!("{err:?}");
        }
    }
}

#[tokio::main]
async fn main() {
    // 读取 OTLP headers 环境变量
    dotenvy::dotenv().ok();
    let _guard = init_tracing_subscriber();

    baz().await;
}

#[tracing::instrument]
async fn foo() {
    tracing::debug!(histogram.baz = 10, "histogram example",);
    tracing::info!(somekey=10, "test value");
    // 测试下来好像只有monotonic_counter和counter能进metrics流
    tracing::info!(
        monotonic_counter.foo = 1_u64,
        key_1 = "bar",
        key_2 = 10,
        "handle foo",
    );
    tracing::debug!(counter.baz = 1_u64, key_1 = "bar", key_2 = 10, "handle foo",);
    log::debug!("hello my friend");
}

#[tracing::instrument]
async fn baz() {
    tracing::info!(somekey=10, "test value");
    // 测试下来好像只有monotonic_counter和counter能进metrics流
    tracing::info!(
        monotonic_counter.foo = 1_u64,
        monotonic_counter.bar = 2_u64,
        key_1 = "bar",
        key_2 = 10,
        "handle foo",
    );
    foo().await;
    tracing::debug!(counter.baz = 1_u64, key_1 = "bar", key_2 = 10, "handle foo",);
    log::debug!("hello my friend");
}